﻿using Quartz;
using Quartz.Impl.Matchers;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace ZYing.QPanel.Impl
{
    public class InProcExecutionHistoryStore : IExecutionHistoryStore
    {
        private readonly ConcurrentDictionary<string, Node> _map = new ConcurrentDictionary<string, Node>();
        private readonly ConcurrentQueue<Node> _queue = new ConcurrentQueue<Node>();
        public int Limit => 500;
        private class Node
        {
            public string Id { get; }
            public Node(ExecutionHistoryEntry entry)
            {
                this.Id = entry.FireInstanceId;
                this._entry = entry;
            }
            private ExecutionHistoryEntry _entry;
            private List<ExecutionLogEntry> _logs;
            public ExecutionHistoryEntry Entry => _entry;
            public void Save(ExecutionHistoryEntry entry)
            {
                if (entry == null) throw new ArgumentNullException(nameof(entry));
                if (entry.FireInstanceId != this.Id) throw new ArgumentException(nameof(entry));
                this._entry = entry;
            }
            public void PushLog(ExecutionLogEntry log)
            {
                lock (this)
                {
                    if (_logs == null) _logs = new List<ExecutionLogEntry>();
                    _logs.Add(log);
                }
            }
            public IReadOnlyList<ExecutionLogEntry> GetLogs()
            {
                if (_logs == null) return Array.Empty<ExecutionLogEntry>();
                return _logs;
            }
            public ExecutionLogEntry FindLastLog()
            {
                if (_logs == null || _logs.Count == 0) return null;
                return _logs[_logs.Count - 1];
            }
        }
        public string SchedulerName { get; set; }
        private int _totalJobsExecuted = 0, _totalJobsFailed = 0;
        public Task<int> GetTotalJobsExecuted()
        {
            return Task.FromResult(_totalJobsExecuted);
        }
        public Task<int> GetTotalJobsFailed()
        {
            return Task.FromResult(_totalJobsFailed);
        }

        public Task IncrementTotalJobsExecuted()
        {
            Interlocked.Increment(ref _totalJobsExecuted);
            return Task.FromResult(0);
        }

        public Task IncrementTotalJobsFailed()
        {
            Interlocked.Increment(ref _totalJobsFailed);
            return Task.FromResult(0);
        }

        public Task Purge()
        {
            return Task.CompletedTask;
        }

        public Task<IReadOnlyList<ExecutionHistoryEntry>> FilterLast(FilterInput input)
        {
            if (input == null || input.Limit <= 0) return Task.FromResult<IReadOnlyList<ExecutionHistoryEntry>>(Array.Empty<ExecutionHistoryEntry>());
            var list = new List<ExecutionHistoryEntry>(input.Limit);
            var nodes = _queue.ToArray();
            var useKeyworkd = !string.IsNullOrWhiteSpace(input.Keyword);
            foreach (var node in nodes)
            {
                var e = node.Entry;
                if (input.JobMatcher != null && !input.JobMatcher.IsMatch(e.Job)) continue;
                if (input.TriggerMatcher != null && !input.TriggerMatcher.IsMatch(e.Trigger)) continue;
                if (useKeyworkd && !IsMatch(e, input.Keyword)) continue;
                if (input.JobState != null)
                {
                    var state = e.GetJobState();
                    if (state != input.JobState) continue;
                }
                list.Add(e);
            }
            list.Reverse();
            if (list.Count > input.Limit) list = list.Take(input.Limit).ToList();
            return Task.FromResult<IReadOnlyList<ExecutionHistoryEntry>>(list);
        }
        private bool IsMatch(ExecutionHistoryEntry entry, string keyword)
        {
            if (entry.Job.Group.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0) return true;
            if (entry.Job.Name.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0) return true;
            if (entry.Trigger.Group.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0) return true;
            if (entry.Trigger.Name.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0) return true;
            if (entry.Class.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0) return true;
            return false;
        }

        public Task<ExecutionHistoryEntry> Find(string fireInstanceId)
        {
            ExecutionHistoryEntry entry = default;
            if (_map.TryGetValue(fireInstanceId, out var node))
            {
                entry = node.Entry;
            }
            return Task.FromResult(entry);
        }

        public Task Save(ExecutionHistoryEntry entry)
        {
            if (_map.TryGetValue(entry.FireInstanceId, out var node))
            {
                node.Save(entry);
            }
            else
            {
                _map[entry.FireInstanceId] = node = new Node(entry);
                _queue.Enqueue(node);
                while (_queue.Count > Limit && _queue.TryDequeue(out node))
                {
                    _map.TryRemove(node.Id, out _);
                }
            }
            return Task.CompletedTask;
        }

        public Task PushStatus(string fireInstanceId, DateTimeOffset time, string status)
        {
            if (_map.TryGetValue(fireInstanceId, out var node))
            {
                node.PushLog(new ExecutionLogEntry { FireTime = time, Status = status });
            }
            return Task.CompletedTask;
        }

        public Task<ExecutionLogEntry> FindLastLog(string fireInstanceId)
        {
            ExecutionLogEntry log = default;
            if (_map.TryGetValue(fireInstanceId, out var node)) log = node.FindLastLog();
            return Task.FromResult(log);
        }

        public Task<IReadOnlyList<ExecutionLogEntry>> GetLogs(string fireInstanceId)
        {
            if (_map.TryGetValue(fireInstanceId, out var node))
            {
                return Task.FromResult(node.GetLogs());
            }
            return Task.FromResult<IReadOnlyList<ExecutionLogEntry>>(Array.Empty<ExecutionLogEntry>());
        }
    }
}
